一般大家提到 canvas 一定會想到一些酷炫的動畫效果,Fabricjs 當然也少不了這些啦!
Fabricjs 提供了簡易的動畫 Api 讓我們可以做一些簡單的動畫效果,透過操作我們自己所新增的物件,讓物件動起來,今天就讓我們一起來玩玩 Fabricjs 提供的動畫吧。
Fabricjs 所有的物件上都能使用 animate
這個方法,就如同 set
這個常用的方法一樣,這邊馬上來試試。
// 動畫練習-角度轉換
rect.animate('angle', 360, {
onChange: canvas.renderAll.bind(canvas)
})
可以看到我們簡單的透過 animate()
這個方法就輕鬆的讓矩形旋轉 360 度,我們稍微看一下帶入的參數:
animate
各種細部的設定,如 duration、動畫時間效果、callbacks為什麼我們要在第三個參數中加入 onChange
呢?這是因為在我們呼叫了 animate
方法後,canvas 會一直更新物件的狀態,angle 會慢慢的從 0~360,所以我們必須要在每個影格都重新繪製物件,讓畫面才有動畫的感覺。
我們在第三個參數內可以設定動畫執行的經過時間要多久。
rect.animate('angle', 360, {
duration: 3000, // 三秒才完成動畫
onChange: canvas.renderAll.bind(canvas)
})
結果
可以在 animate 第三個參數內加入 easing 來變更動畫進行時的效果,這邊可用 fabric.util.ease
所提供的一些效果。
rect.animate('angle', 360, {
duration: 1000,
onChange: canvas.renderAll.bind(canvas),
easing: fabric.util.ease.easeInOutBack
})
可以看到我們加上了 easing: fabric.util.ease.easeInOutBack
後,呈現了不一樣的動畫效果。
這邊 fabric.util.ease 還提供了更多有趣的動畫效果
fabricjs doc - http://fabricjs.com/docs/fabric.util.ease.html
我們也可以透過相對位置累加的方式,來做動畫移動的效果。
// 動畫練習-角度轉換
rect.animate('left', '+=500', {
onChange: canvas.renderAll.bind(canvas)
})
可以把動畫效果合在一起,做出更豐富的效果
這邊結合上面兩個動畫
控制 100 顆球球的位置、大小、透明度。
首先我們要產生靜態的 canvas 因為我們不需要去操作他們,並且定義一個 playing
的 Boolean 型態變數方便我們之後去操控動畫的動或不動。
// 視窗大小
const windowSize = {
width: window.innerWidth,
height: window.innerHeight
}
// 產生靜態 canvas
const canvas = new fabric.StaticCanvas('canvas', {
height: windowSize.height, // 讓畫布同視窗大小
width: windowSize.width
})
let playing = true // 預設開啟
我們需要一個方便產生亂數的函數,讓我們動畫更加有變化性
// 取得亂數
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
使用迴圈產生 100 個圓形,並且為他們設定動畫,這邊我把動畫放在另外一個函數,須把 circle
物件傳入 setAnimate
這邊有一個重點是必須要記錄哪個圓形是最後被產生的,方便我們之後重整畫布。
circle.lastAdd = i === 99
這邊
// add 100 circle
for (let i = 0; i<100; i++) {
// 產生圓形,並給定隨機起始值
let circle = new fabric.Circle({
radius: getRandomInt(2, 15),
left: getRandomInt(0, windowSize.width),
top: getRandomInt(0, windowSize.height),
opacity: getRandomInt(0.1, 1)
})
// 紀錄一下自己是第幾個被產生的 circle
circle.lastAdd = i === 99
canvas.add(circle)
// 設定動畫
playing && setAnimate(circle)
}
這邊就是最重要的設定動畫的函數啦!
這邊分別設定了以下四個動畫效果,結合剛剛的 getRandomInt
,讓動畫更加隨機更有趣!
// 設定動畫函數
function setAnimate (circle) {
// 變化半徑
circle.animate('radius', getRandomInt(2, 15), {
duration: getRandomInt(1000, 5000)
})
// 變化透明度
circle.animate('opacity', getRandomInt(0, 1), {
duration: getRandomInt(1000, 5000)
})
// 變化座標
circle.animate('left', getRandomInt(0, windowSize.width), {
easing: fabric.util.ease.easeInOutCubic,
duration: getRandomInt(1000, 5000)
})
// 變化座標
circle.animate('top', getRandomInt(0, windowSize.height), {
onChange: () => {
// 不需要每個 circle 都呼叫 canvas.renderAll()
// 只有最後一個被新增的物件 onChange 去更新畫布
if (circle.lastAdd) canvas.renderAll()
},
onComplete: () => playing && setAnimate(circle),
easing: fabric.util.ease.easeInOutCubic,
duration: getRandomInt(1000, 5000)
})
}
重點在最後一個動畫設置,幫大家拿出來看一下。
circle.animate('top', getRandomInt(0, windowSize.height), {
onChange: () => {
// 不需要每個 circle 都呼叫 canvas.renderAll()
// 只有最後一個被新增的物件 onChange 去更新畫布
if (circle.lastAdd) canvas.renderAll()
},
onComplete: () => playing && setAnimate(circle),
easing: fabric.util.ease.easeInOutCubic,
duration: getRandomInt(1000, 5000)
})
我們只需要在最後一個 circle.animate
設定動畫來加入 onChange
以及 onComplete
函數,不然會重複設置 4 次,造成動畫的效能降低。
我們再來一一的看一下他們發生了什麼。
onChange
這邊需要判斷是否為最後一個被新增的 circle
物件,否則會重複呼叫 canvas.renderAll()
這個重整函數。(100 個物件就會重複呼叫 99 次),造成效能變差。
onComplete
這邊很直覺的就是當我們 circle
物件動畫做完後就繼續做新的一次動畫,讓我們看起來動畫是連續的。
透過一開始設置的 playing
變數,來控制動畫是否繼續,canvas.getObjects()
抓取所有在 canvas 之下的所有 circle
物件。透過 forEach()
,在一次地將所有 circle
物件加入動畫效果。
// 左上方按鈕
document.querySelector('#toggle').addEventListener('click', (e) => {
const targetEl = e.target
if (playing) {
targetEl.innerHTML = 'start'
} else {
targetEl.innerHTML = 'stop'
// 讓所有物件在一次動起來
canvas.getObjects().forEach(circle => setAnimate(circle))
}
playing = !playing
})
完整程式碼 - https://codepen.io/nono1526/pen/BqqBxo
簡單練習了 fabricjs 動畫的使用
今日 codepen 連結